
import _ from 'lodash'
import { ReadFileHelp, BaseDataNode, DataType } from './../base-model'


export class Int32Value extends BaseDataNode {
    get data_type(): DataType { return "number"; }

    length = 4;
    value?: number;

    get inner(): BaseDataNode[] {
        return [];
    }

    async after(): Promise<BaseDataNode | undefined> {
        return undefined;
    }

    protected async readFromBlob(blob: Blob): Promise<this["value"]> {
        let buff = await ReadFileHelp.readArrayBuffer(blob.slice(this.offset, this.offset + this.length));
        if (buff) {
            let int32Array = new Int32Array(buff);
            return int32Array[0];
        }
    }
    protected async readFromBuffer(buffer: ArrayBuffer): Promise<this["value"]> {
        let buff = await buffer.slice(this.offset, this.offset + this.length);
        if (buff) {
            let int32Array = new Int32Array(buff);
            return int32Array[0];
        }
    }

    async doAnalyze(): Promise<void> {
        await this.readValue();
    }

    toString() {
        return this.value;
    }
}

export class Utf8String extends BaseDataNode {

    get data_type(): DataType { return "string"; }

    value?: string;

    get inner(): BaseDataNode[] {
        return [];
    }
    async after(): Promise<BaseDataNode | undefined> {
        return undefined;
    }

    protected async readFromBlob(blob: Blob): Promise<this["value"]> {
        return await ReadFileHelp.readText_Until0(
            this.offset > 0 || (this.length && this.length < blob.size)
                ? blob.slice(
                    this.offset,
                    this.length ? this.offset + this.length : undefined
                )
                : blob,
            "utf-8",
            byte_length => this.length = byte_length || 0
        );
    }
    protected async readFromBuffer(buffer: ArrayBuffer): Promise<this["value"]> {
        let sub_blob = new Blob([buffer.slice(this.offset)]);
        return await ReadFileHelp.readText_Until0(sub_blob, "utf-8", byte_length => this.length = byte_length || 0);
    }

    async doAnalyze(): Promise<void> {
        await this.readValue();
    }

    toString() {
        return this.value;
    }
}


/**
 * 根节点
 */
export class RootNode extends BaseDataNode {

    get data_type(): DataType { return "custom"; }

    value?: {
        name: Utf8String,
        version: Int32Value,
        head: HeadArea,
        data: DataArea
    };
    //is_read = true;

    //onBeforeAnalyze?: (node: this) => Promise<void>
    //onAfterAnalyze?: (node: this) => Promise<void>

    get inner(): BaseDataNode[] {
        return this.value
            ? [this.value.name, this.value.version, this.value.head, this.value.data]
            : [];
    }

    async after(): Promise<BaseDataNode | undefined> {
        return undefined;
    }

    private createValue(src: Blob | ArrayBuffer): this["value"] {
        return {
            name: new Utf8String()._set({ name: "name", length: 4, data_source: src.slice(0, 4) }),
            version: new Int32Value()._set({ name: "version", data_source: src.slice(4, 8) }),
            head: new HeadArea()._set({ name: "head", data_source: src.slice(12, 40) }),
            data: new DataArea()._set({ name: "data", data_source: src }) // 数据的坐标以文件头为0，故不切片
        };
    }

    protected async readFromBlob(blob: Blob): Promise<this["value"]> {
        return this.createValue(blob);
    }

    protected async readFromBuffer(buffer: ArrayBuffer): Promise<this["value"]> {
        return this.createValue(buffer);
    }

    protected async doAnalyze(): Promise<void> {
        if (this.data_source) {
            this.length = this.data_source instanceof Blob ? this.data_source.size : this.data_source.byteLength;

            await this.readValue();

            if (this.value) {
                this.value.head.onAfterAnalyze = async head => {
                    if (this.value && head.value) {
                        this.value.data.text_addr_area_offset = head.value.text_addr_offset.value || 0;
                        this.value.data.text_value_area_offset = head.value.text_value_offset.value || 0;
                    }
                };
                await this.value.name.analyze();
                await this.value.version.analyze();
                await this.value.head.analyze();
            }
        }
    }

}

class HeadArea extends BaseDataNode {

    get data_type(): DataType { return "custom"; }

    length = 28;
    value?: {
        data_offset_1: Int32Value,
        /** 文本数据地址区偏移 */
        text_addr_offset: Int32Value,
        /** 文本数据区偏移 */
        text_value_offset: Int32Value,
        data_offset_4: Int32Value,
        data_offset_5: Int32Value,
        data_offset_6: Int32Value,
        data_offset_7: Int32Value
    };

    get inner(): BaseDataNode[] {
        return this.value
            ? [
                this.value.data_offset_1,
                this.value.text_addr_offset,
                this.value.text_value_offset,
                this.value.data_offset_4,
                this.value.data_offset_5,
                this.value.data_offset_6,
                this.value.data_offset_7
            ]
            : [];
    }

    private createValue(src: Blob | ArrayBuffer): this["value"] {
        return {
            data_offset_1: new Int32Value()._set({ offset: this.offset, data_source: src }),
            text_addr_offset: new Int32Value()._set({ name: "text_addr_offset", offset: this.offset + 4, data_source: src }),
            text_value_offset: new Int32Value()._set({ name: "text_value_offset", offset: this.offset + 8, data_source: src }),
            data_offset_4: new Int32Value()._set({ offset: this.offset + 12, data_source: src }),
            data_offset_5: new Int32Value()._set({ offset: this.offset + 16, data_source: src }),
            data_offset_6: new Int32Value()._set({ offset: this.offset + 20, data_source: src }),
            data_offset_7: new Int32Value()._set({ offset: this.offset + 24, data_source: src })
        };
    }

    protected async readFromBlob(blob: Blob): Promise<this["value"]> {
        return this.createValue(blob);
    }
    protected async readFromBuffer(buffer: ArrayBuffer): Promise<this["value"]> {
        return this.createValue(buffer);
    }
    protected async doAnalyze(): Promise<void> {
        if (this.data_source) {
            await this.readValue();

            if (this.value) {
                await this.value.data_offset_1.analyze();
                await this.value.text_addr_offset.analyze();
                await this.value.text_value_offset.analyze();
                await this.value.data_offset_4.analyze();
                await this.value.data_offset_5.analyze();
                await this.value.data_offset_6.analyze();
                await this.value.data_offset_7.analyze();
            }

        }
    }
}

class DataArea extends BaseDataNode {

    get data_type(): DataType { return "custom"; }

    text_addr_area_offset = 0;
    text_value_area_offset = 0;

    value?: any;

    get inner(): BaseDataNode[] {
        return [];
    }

    private createValue(src: Blob | ArrayBuffer): this["value"] {
        return {
        };
    }
    protected async readFromBlob(blob: Blob): Promise<this["value"]> {
        return this.createValue(blob);
    }
    protected async readFromBuffer(buffer: ArrayBuffer): Promise<this["value"]> {
        return this.createValue(buffer);
    }

    protected async doAnalyze(): Promise<void> {
        if (this.data_source) { }
    }
}

class ResTextCollection extends BaseDataNode {
    get data_type(): DataType { return "custom"; }

    //text_value_area_offset = 0;

    page_size = 20
    page_index = 0
    item_count = 0

    items: ResTextItem[] = []
    value?: ResTextItem[]

    get inner(): BaseDataNode[] {
        return this.value || [];
    }

    private readPage(src: Blob | ArrayBuffer): this["value"] {
        let ret: ResTextItem[] = [];
        if (this.item_count > 0) {
            let index_from = _.min([this.page_index * this.page_size, this.item_count - 1]) || 0;
            let index_to = _.min([(this.page_index + 1) * this.page_size - 1, this.item_count - 1]) || 0;
            for (let i = index_from; i <= index_to; i++) {
                if (!this.items[i]) {
                    this.items[i] = new ResTextItem()._set({ offset: this.offset + i * 4, data_source: src });
                }
                ret.push(this.items[i]);
            }
        }
        return ret;
    }

    protected async readFromBlob(blob: Blob): Promise<this["value"]> {
        return this.readPage(blob);
    }
    protected async readFromBuffer(buffer: ArrayBuffer): Promise<this["value"]> {
        return this.readPage(buffer);
    }
    protected async doAnalyze(): Promise<void> {
        if (this.data_source) {
            this.page_index = 0;
            this.item_count = Math.floor(this.length / 4);
            await this.analyze();
        }
    }


}

class ResTextItem extends BaseDataNode {

    get data_type(): DataType { return "custom"; }

    value?: {
        addr: Int32Value,
        text: Utf8String
    }

    get inner(): BaseDataNode[] {
        return this.value
            ? [this.value.addr, this.value.text]
            : [];
    }

    private createValue(src: Blob | ArrayBuffer): this["value"] {
        return {
            addr: new Int32Value()._set({ name: "addr", offset: this.offset, data_source: src }),
            text: new Utf8String()._set({ name: "text", data_source: src })
        };
    }

    protected async readFromBlob(blob: Blob): Promise<this["value"]> {
        return this.createValue(blob);
    }
    protected async readFromBuffer(buffer: ArrayBuffer): Promise<this["value"]> {
        return this.createValue(buffer);
    }
    protected async doAnalyze(): Promise<void> {
        if (this.data_source) {
            await this.readValue();
            if (this.value) {
                this.value.addr.onAfterAnalyze = async addr => {
                    if (this.value) {
                        this.value.text.offset = addr.value || 0;
                        await this.value.text.analyze();
                    }
                };
            }
        }
    }

    toString() {
        return this.value ? `[${this.value.addr.toString()}] ${this.value.text.toString()}` : "";
    }

}